{%-import 'loader.jinja2' as loader-%}
{%-set exception %}{{resolver.cpp_get_lib_ns() | join('::')}}::JsonSchemaException{%endset%}
{%macro NestedObjectName(name) -%}
    {{-name | UpperCamelCase-}}
{%-endmacro%}
{%-macro ObjectType(propName, propSchema) -%}
    {%-if '$ref' in propSchema -%}
        {{-loader.Reference(resolver, propSchema['$ref'])-}}
    {%-else-%}
        {{-NestedObjectName(propName)-}}
    {%-endif-%}
{%-endmacro%}
{%-set optionList -%}
{%-for s in schema.oneOf -%}{%set optionName%}Option{{loop.index}}{%endset-%}
{{ObjectType(optionName, s)}}{%if not loop.last%}, {%endif-%}
{%-endfor -%}
{%-endset%}
/*! {{Name}} is a wrapper around one of {{schema.oneOf|length}} different types.
 {%-if schema.description %}
 *
 {{schema.description | indent(1) }}
 *
 {%-endif%}
 */
class {{Name}}
{
public:
    {%-for s in schema.oneOf %}{%set optionName%}Option{{loop.index}}{%endset%}
    {{loader.Class('hpp', resolver, [Name], optionName, s) | indent(4) }}
    
    {%endfor%}

    {%-for s in schema.oneOf %}{%set optionName%}Option{{loop.index}}{%endset%}
    /*! Constructor that initializes the {{Name}} object with a {{ObjectType(optionName, s)}} instance.]
     */
    {{Name}}(const {{ObjectType(optionName, s)}}& obj);
    {%endfor%}
    virtual ~{{Name}}() = default;

    {%-set commonType = schema.GetCommonType(resolver)%}
    {%-if commonType in ['boolean', 'integer', 'number', string] %}
    /*! Stream operator.
    */
    friend std::ostream& operator<<(std::ostream& os, const {{Name}}& inst);
    {%-endif%}

    /*! Produce a hash value of the object.
     * \param inst A {{Name}} instance from which to produce a hash.
     * \return a hash number using a boost hashing algorithm.
     */
    friend std::size_t hash_value(const {{Name}}& inst);

    /*! Gets a variant object containing whatever instance of whatever object has been set.
     */
    boost::variant<{{optionList}}> Get() const;

    {#{%-for s in schema.oneOf -%}{%set optionName%}Option{{loop.index}}{%endset-%}
    /*! Asserts that the type provided is a {{ObjectType(optionName, s)}}.
     * Asserts that when `object.Get().which() == {{loop.index - 1}}` the expression `boost::get<{{ObjectType(optionName, s)}}>(object.Get())` is valid.
     * This is static_assertion, so it will break at compile time, which is desireable.
     * \code
       boost::variant<{{optionList}}> v = instance.Get();
       if (v.which() == {{loop.index - 1}})
       {
           {{Name}}::StaticAssertWhich{{loop.index - 1}}Type<{{ObjectType(optionName, s)}}>();
           {{ObjectType(optionName, s)}} opt = boost::get<{{ObjectType(optionName, s)}}>(v);
           // do other stuff with opt
       }
     * \endcode
     */
    template <typename T>
    static inline void StaticAssertWhich{{loop.index - 1}}Type()
    {
        static_assert(std::is_same<T, {{ObjectType(optionName, s)}}>::value, "Type {{loop.index - 1}} is not {{ObjectType(optionName, s)}}");
    }
    {%-endfor -%}#}

    /*! Sets the variant object.
     * \fn void Set(const boost::variant<{{optionList}}>& variant)
     * \param variant  An instance of one of these types must be passed: {{optionList}}
     */
    void Set(const boost::variant<{{optionList}}>& variant);

    {%-if commonType in ['boolean', 'integer', 'number', string] %}
    /*! \fn {{Name}} FromString(const std::string& str)
     * \brief Performs a "lexical cast" on the string to create a new {{Name}} object
     * \param const std::string& str stringified representation of a {{schema.GetCommonType(resolver)}}
     * \throw {{exception}} If the string cannot be cast or does not JSON-schema validate
     * \return {{Name}}
     */
    static {{Name}} FromString(const std::string& str);
    {%-endif%}

    /*! \fn {{Name}} FromJson(const rapidjson::Value& json)
     * \brief Deserializes a JSON value into a new instance of {{Name}}.
     * \param json is the RapidJSON value.
     * \throw {{exception}} If the JSON didn't validate according to the schema of one of the component objects.
     * \returns {{Name}}
     */
    static {{Name}} FromJson(const rapidjson::Value& json);

    /*! \fn ToJson(rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator)
     * \brief Sets 'value' to whatever variant object is set
     * \param value is the RapidJSON value which will be modified to contain the serialization
     * \param allocator is the top-level RapidJSON document allocator which may be used for allocations
     */
    void ToJson(rapidjson::Value& value, rapidjson::Value::AllocatorType& allocator) const;

    /*! Sets a string handle associated with this {{Name}} instance.
     * This gets called by a parent object after creating an instance that is used for an object's property.
     * \param handle is the string name.
     */
    void SetHandle(const std::string& handle);

    /*! Gets the string handle associated with this {{Name}} instance.
     * This is often the property name used in a JSON-object parent.
     * It may be empty.
     * \returns the handle string
     */
    std::string GetHandle() const;
private:
    boost::variant<{{optionList}}> _value;
    std::string _handle;
};
